﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TDNA
{
    public class TDGame
    {
        public List<BasicTower> TowerList { get; private set; }
        public List<BasicCreep> EnemyList { get; private set; }

        public BasicLevel CurLevel {  get; set; }

        public TDGame()
        {
            CurLevel = new BasicLevel(15);
            CurLevel.HP = 5;
            TowerList = new List<BasicTower>();
            EnemyList = new List<BasicCreep>();
        }

        public void SpawnCreep(BasicCreep creep)
        {
            EnemyList.Add(creep);
            creep.Route = getRoute(CurLevel.Start, CurLevel.End);
            creep.Pos = CurLevel.Start;
        }

        public bool AddTower(BasicTower tower)
        {
            if (!legitTower(tower)) return false;

            TowerList.Add(tower);
            CurLevel.Grid[(int)tower.Pos.X, (int)tower.Pos.Y] = tower;

            updateEnemyRoute();
            return true;
        }
        /// <summary>
        /// Überprüft ob ein Tower gesetzt werden kann
        /// </summary>
        /// <param name="tower"></param>
        /// <returns></returns>
        private bool legitTower(BasicTower tower)
        {
            if (!(CurLevel.Grid[(int)tower.Pos.X, (int)tower.Pos.Y] is FlatGameObj)) return false; //Tower können nur auf FlatGameObj gesetzt werden
            FlatGameObj fgobj = (CurLevel.Grid[(int)tower.Pos.X, (int)tower.Pos.Y] as FlatGameObj);
            CurLevel.Grid[(int)tower.Pos.X, (int)tower.Pos.Y] = tower;

            Stack<tdVector2> Path = getRoute(this.CurLevel.Start, this.CurLevel.End);
            if (Path.Count == 0) { CurLevel.Grid[(int)tower.Pos.X, (int)tower.Pos.Y] = fgobj; return false; }

            foreach(BasicCreep Creep in EnemyList)
            {
                if (Creep.Route.Count < 1) continue;
                Path = getRoute(Creep.Route.Peek(), CurLevel.End);
                if (Path.Count == 0)
                {
                    CurLevel.Grid[(int)tower.Pos.X, (int)tower.Pos.Y] = fgobj;
                    return false;
                }
            }

            CurLevel.Grid[(int)tower.Pos.X, (int)tower.Pos.Y] = fgobj;
            return true;
        }

        private void updateEnemyRoute()
        {
            foreach (BasicCreep Creep in EnemyList)
            {
                if (Creep.Route.Count > 0)
                {
                    Stack<tdVector2> Path = getRoute(Creep.Route.Peek(), CurLevel.End);
                    Creep.SetRoute(Path);
                }            
            }
        }


        private List<BasicCreep> getAttackList(BasicTower t)
        {
           List<BasicCreep> myList = new List<BasicCreep>();

           foreach(BasicCreep Creep in EnemyList)
           {
               if (tdVector2.Dist(Creep.Pos, t.Pos) <= t.Range) myList.Add(Creep);
           }

           return myList;
        }


        public void Update(ulong gameTime)
        {
            for (int i = EnemyList.Count-1; i >=  0; i--)
            {
                EnemyList[i].Update(gameTime);
                if (EnemyList[i].HP <= 0) { EnemyList[i] = null; EnemyList.RemoveAt(i); continue; }
                if (tdVector2.Equals(EnemyList[i].Pos,CurLevel.End)) 
                {
                    EnemyList[i] = null; EnemyList.RemoveAt(i);
                    CurLevel.HP -= 1;
                }
            }
            for (int i = 0; i < TowerList.Count; i++)
            {
                TowerList[i].Update(gameTime);
                if (TowerList[i] is CannonTower)
                {
                    if ((TowerList[i] as CannonTower).Cannon.Target == null)
                    {
                        (TowerList[i] as CannonTower).Creeps = getAttackList(TowerList[i]);
                    }
                }
            }
        }

        //A*Baby!
        private Stack<tdVector2> getRoute(tdVector2 start, tdVector2 end)
        {
            List<int> OpenList = new List<int>();
            List<bool> Besucht = new List<bool>();
            List<float> Cost = new List<float>(); //Kostenliste
            List<int> Pre = new List<int>();

            //Init Kosten
            for (int i = 0; i < CurLevel.Grid.GetLength(0) * CurLevel.Grid.GetLength(1); i++)
            {
                Cost.Add(float.MaxValue);
                Pre.Add(0);
                Besucht.Add(false);
            }

            tdVector2 CurNode;

            //Start Knoten Adden
            OpenList.Add(vecToIncCoord(start));

            //Besucht setzen
            Pre[vecToIncCoord(start)] = -1;
            Cost[vecToIncCoord(start)] = 0;
            Besucht[vecToIncCoord(start)] = true;

            while (OpenList.Count > 0)
            {
                //hole kleinsten Knoten aus der Open List
        
                List<float> tmp = new List<float>();
                for (int i = 0; i < OpenList.Count; i++) tmp.Add(tdVector2.Dist(intToVecCoord(OpenList[i]), end));
                int IndexMin = tmp.IndexOf(tmp.Min());
                int tmp2 = OpenList[IndexMin];
                OpenList.RemoveAt(IndexMin);

                CurNode = intToVecCoord(tmp2);

                //Wow schon am Ende? Baue Route auf und schicke sie zurueck!
                if (tdVector2.Equals(CurNode, end))
                { 
                    Stack<tdVector2> Result = new Stack<tdVector2>();
                    Result.Push(end);
                    int cur = vecToIncCoord(end); // (int)end.Y * CurLevel.Grid.GetLength(0) + (int)end.X;
                    while (cur >= 0)
                    {
                        Result.Push(intToVecCoord(cur));
                        cur = Pre[cur];
                    }

                    return Result; 
                }

                //Wohin als nächstes?
                expandNode(CurNode, OpenList, Besucht, Cost, end,Pre);
                Besucht[vecToIncCoord(CurNode)] = true;     
            }
            return new Stack<tdVector2>();
        }

        private void expandNode(tdVector2 _CurNode, List<int> _OpenList, List<bool> _Besucht, List<float> _Cost, tdVector2 _End,  List<int> _Pre)
        { 
                for(int i = -1; i <= 1; i++)
                    for (int j = -1; j <= 1; j++)
                    {
                        tdVector2 tmpNode = _CurNode + new tdVector2(i, j);

                        if (tdVector2.Equals(_CurNode,tmpNode)) continue;
                        //Grenzen überschritten?
                        if (tmpNode.X < 0 || tmpNode.X >= CurLevel.Grid.GetLength(0) ||
                            tmpNode.Y < 0 || tmpNode.Y >= CurLevel.Grid.GetLength(1)) continue;
                        if (Math.Abs(i) == Math.Abs(j)) continue; 

                        //Gebiet blockiert?
                        if (CurLevel.Grid[(int)tmpNode.X, (int)tmpNode.Y] is BasicTower) continue;
                        //Knoten schon besucht?
                        if (_Besucht[vecToIncCoord(tmpNode)]) continue;
                        //Neuen Abstand berechnen
                        float Dist = _Cost[vecToIncCoord(_CurNode)] + 1; 
                        //Breche ab wenn der Knoten auf der Open List ist und die neue Distanz nicht besser ist
                        bool onList = _OpenList.Contains(vecToIncCoord(tmpNode));
                        bool better = (Dist < _Cost[vecToIncCoord(tmpNode)]);
                        if(onList && !better) continue;
                        //Vorgaengerzeiger setzen 
                        if(!onList) _OpenList.Add(vecToIncCoord(tmpNode));
                        _Pre[vecToIncCoord(tmpNode)] = vecToIncCoord(_CurNode);
                        _Cost[vecToIncCoord(tmpNode)] = _Cost[vecToIncCoord(_CurNode)] + 1;
                        //Knoten zur OpenList hinzufügen
                        if (!_OpenList.Contains(vecToIncCoord(new tdVector2(_CurNode.X + i, _CurNode.Y + j)))) _OpenList.Add(vecToIncCoord(new tdVector2(_CurNode.X + i, _CurNode.Y + j)));
                    
                    }
        }
        
        private tdVector2 intToVecCoord(int input)
        { 
            return new tdVector2(input % CurLevel.Grid.GetLength(0),  (int) ( (int)input/CurLevel.Grid.GetLength(0)));   
        }

        private int vecToIncCoord(tdVector2 input)
        { 
            return ((int)input.Y * CurLevel.Grid.GetLength(0) + (int) input.X);
        }
    }
}
